/*
 * Project   : Provide support for OLEDB
 * Purpose   : Declare the ClConnection class that handles OLEDB consumers.
 * Author    : Eduardo Sobrino
 * Date      : Feb/2k1
 * Update    : Jun/2k7 - Move code to Kif Framework and added replaced banded
 *           :         - functions moving to "secure-string" handling...
 */

#pragma region   // Include Files

#include "stdafx.h"

#define DBINITCONSTANTS // Initialize OLE constants...
#define INITGUID        // ...once in each app.

#include "Win32.Data.OleDb.h"
#include <comdef.h>
#include <sqloledb.h>

#ifdef ORAOLEDB
#include <oraOleDb.h>
#endif

#pragma endregion

// -----------------------------------------------------------------------------

namespace Kif
{
namespace Win32
{
namespace Data
{
namespace OleDbClient
{

// -----------------------------------------------------------------------------
// Define the frequently use constants

#define DBMAX_TEXT_FIELD_LEN 1024
#define MaxStringLength Kif::Win32::String::MaxStringLength

// -----------------------------------------------------------------------------
#pragma region // Define the DbConnection class

/*
 * Function  : DbResult *DbConnection::PrepareProcedure(
 *           :    TCHAR *pProcName, DbParams *pParams)
 * Purpose   : Given a procedure name prepare the stored procedure command.
 * In        : pProcName = pointer to procedure name (TCHAR*)
 *           : pColumns  = procedure result columns (DbFields*)
 *           : pParams   = procedure parameters (DbParams*)
 * Date      : Mar/2k2  (ESob)
 */

DbResult *DbConnection::PrepareProcedure(
   TCHAR *pProcName, DbFields *pColumns, DbParams *pParams)
{

   // prepare command

   int i;
   TCHAR qstr[MaxStringLength+1];

   _tcscpy_s(qstr,MaxStringLength,TEXT("{ ? = call "));
   _tcscat_s(qstr,MaxStringLength,pProcName);

   if (pParams->iCount)
      _tcscat_s(qstr,MaxStringLength,TEXT(" ("));
   else
      _tcscat_s(qstr,MaxStringLength,TEXT(" "));
   for (i = 1; i < (int)pParams->iCount; i++)
   {
      if (i < (int)(pParams->iCount - 1))
         _tcscat_s(qstr,MaxStringLength,TEXT("?,"));
      else
         _tcscat_s(qstr,MaxStringLength,TEXT("?"));
   }
   if (pParams->iCount)
      _tcscat_s(qstr,MaxStringLength,TEXT(") "));

   _tcscat_s(qstr,MaxStringLength,TEXT("}"));

   // get pointer of command
   DbResult *pCommand = Prepare( qstr,pColumns,pParams);

   return(pCommand);
}  // end of PrepareProcedure

/*
 * Function  : DbConnection::ExecuteProcedure(TCHAR *pProcName)
 * Purpose   : Execute a given stored procedure, true is returned if
 *           : successfull.
 * In        : pProcName = pointer to procedure name (TCHAR*)
 * Date      : Mar/2k2  (ESob)
 */

bool DbConnection::ExecuteProcedure(TCHAR *pProcName)
{  bool done = false;

   // allocate and prepare parameters ...

   TDbInt iRetVal;
   DbParams *pParams = AllocParams(1);
   if (pParams) {

      // prepare parameters ...

      pParams->Set(0,&iRetVal);
      pParams->IsForOutput(0);
   }
   else
      return(done);

   // prepare command

   TCHAR qstr[MaxStringLength+1];

   _tcscpy_s(qstr,MaxStringLength,TEXT("{ ? = call "));
   _tcscat_s(qstr,MaxStringLength,pProcName);
   _tcscat_s(qstr,MaxStringLength,TEXT(" }"));

   DbResult *pRecordSet = Prepare( qstr,NULL,pParams);

   if (pRecordSet) {

      // execute command

      done = pRecordSet->ExecuteNoneQuery();
   }

   // release allocated resources

   pParams->Free();
   delete pParams;
   if (pRecordSet)
      delete pRecordSet;

   return(done);
}  // end of execute given procedure

/*
 * Function : ULONG DbConnection::BindField(DBBINDING *pField)
 * Purpose  : Bind field.
 * In       : pField = field binding info (DBBINDING*)
 * Date     : Feb/2k1  (ESob)
 */

ULONG DbConnection::BindField(DBBINDING *pField)
{  ULONG ulColLen = 0;

   // If the provider's native data type for this column is
   // DBTYPE_IUNKNOWN or this is a BLOB column and the user
   // has requested that we bind BLOB columns as ISequentialStream
   // objects, bind this column as an ISequentialStream object if
   // the provider supports our creating another ISequentialStream
   // binding.

   if (IsBlobField(pField)) {

      // pField->dwFlags  = NULL;
      // pField->wType    = EDbChar;
      // pField->cbMaxLen = ulColLen = MINSTRLEN;

      // the following are ignore for BLOBS or very large objects ...

      pField->bPrecision = 0;
      pField->bScale     = 0;

      // precompilation MACRO will instantiate the code to handle BLOBS
      // as bytes or through ISequentialStream ...

      #ifdef DB_TEXT_AS_BYTE

         pField->wType    = DBTYPE_BYTES;
         pField->cbMaxLen = sizeof(BYTE)*(DB_TEXT_MAX_LEN+1);
         ulColLen = pField->cbMaxLen;

      #else

         // To create an ISequentialStream object, we will
         // bind this column as DBTYPE_IUNKNOWN to indicate
         // that we are requesting this column as an object.

         pField->wType = DBTYPE_IUNKNOWN;

         // We want to allocate enough space in our buffer for
         // the ISequentialStream pointer we will obtain from
         // the provider.

         ulColLen = sizeof(ISequentialStream *);
         pField->cbMaxLen = 0;          // does not apply

         // To specify the type of object that we want from the
         // provider, we need to create a DBOBJECT structure and
         // place it in our binding for this column.

         pField->pObject =
            (DBOBJECT *)g_pIMalloc->Alloc(sizeof(DBOBJECT));

         // further prepare for ISequentialStream for reading BLOB
         // or deal with the memory allocation failure ...

         if (pField->pObject) {

            // Direct the provider to create an ISequentialStream
            // object over the data for this column.

            pField->pObject->iid = IID_ISequentialStream;

            // We want read access on the ISequentialStream
            // object that the provider will create for us.

            pField->pObject->dwFlags = STGM_READ;

         }
         else {

            // TODO : Through an exception here or deal with the fact
            //      : that storage object couldn't be allocated do
            //      : to memory allocation failure ...

         }

      #endif
   }
   else {

      // make adjustments to buffer max len for string types ...

      switch (pField->wType)
      {
         case DBTYPE_STR:
         case DBTYPE_WSTR:
         case DBTYPE_BSTR:

            // for know we will not handle char strings to long ...

            if (pField->cbMaxLen > DBMAX_TEXT_FIELD_LEN)
                pField->cbMaxLen = DBMAX_TEXT_FIELD_LEN;

            pField->cbMaxLen += sizeof(TCHAR);
            break;
      }

      ulColLen = pField->cbMaxLen;
   }

   return(ulColLen);
}  // end of bind blob field

/*
 * Function : ULONG DbFields::BindFields(DBBINDING *pDbBindings)
 * Purpose  : Bind field list.
 * In       : pDbBindings = vector with binding info (DBBINDING*)
 * Date     : Feb/2k1  (ESob)
 */

ULONG DbFields::BindFields(DBBINDING *pDbBindings)
{  ULONG ulRowLen = 0;
   UINT  nCol;

   DBPART dwPart = pRowBuffer ? DBPART_VALUE : DBPART_STATUS|DBPART_VALUE;
   DBBYTEOFFSET exOffset = pRowBuffer ? 0 : sizeof(DBSTATUS);

   for (nCol = 0; nCol < iCount; nCol++)
   {
      pDbBindings[nCol].iOrdinal   = nCol+1;
      pDbBindings[nCol].obLength   = 0;
      pDbBindings[nCol].obStatus   = exOffset ? ulRowLen : 0;
      pDbBindings[nCol].obValue    = ulRowLen+exOffset;
      pDbBindings[nCol].pTypeInfo  = NULL;
      pDbBindings[nCol].pObject    = NULL;
      pDbBindings[nCol].pBindExt   = NULL;
      pDbBindings[nCol].dwPart     = dwPart;
      pDbBindings[nCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
      pDbBindings[nCol].eParamIO   = pList[nCol].eParamIO;
      pDbBindings[nCol].cbMaxLen   = pList[nCol].iSizeInBytes;
      pDbBindings[nCol].dwFlags    = 0;
      pDbBindings[nCol].wType      = pList[nCol].eType;

      // the precision and scale info is ignore unless
      // wType is NUMERIC, VARNUMERIC or DECIMAL

      pDbBindings[nCol].bPrecision = pList[nCol].iSize;
      pDbBindings[nCol].bScale     = pList[nCol].iDec;

      // call parent bind field function to make sure we can manage
      // special cases ...

      if (pParentObj)
         pDbBindings[nCol].cbMaxLen = pParentObj->BindField(pDbBindings+nCol);

      // calculate start address for next value buffer

      ulRowLen += pDbBindings[nCol].cbMaxLen+exOffset;

      // align memory ...

      ulRowLen = ROUNDUP(ulRowLen);
   }

   return(ulRowLen);
}  // end of bind fields ...

/*
 * Function : ULONG DbFields::BindParams(DBPARAMBINDINFO *pDbBindings)
 * Purpose  : Bind parameter list.
 * In       : pDbBindings = vector with binding info (DBPARAMBINDINFO*)
 * Date     : Feb/2k1  (ESob)
 */

ULONG DbFields::BindParams(DBPARAMBINDINFO *pDbBindings)
{  ULONG ulRowLen = 0;
   UINT  nCol;
   wchar_t *pwTypeName;

   for (nCol = 0; nCol < iCount; nCol++)
   {

      // get type name for convertion specification ...

      switch (pList[nCol].eType)
      {
         case EDbInteger:
            pwTypeName = TEXT("DBTYPE_I4");
            break;

         case EDbDouble:
            pwTypeName = TEXT("DBTYPE_R8");
            break;

         case EDbChar:
            pwTypeName = TEXT("DBTYPE_CHAR");
            break;

         case EDbDatetime:
            pwTypeName = TEXT("DBTYPE_DBTIMESTAMP");
            break;

         default :
            pwTypeName = NULL;
      }

      // set parameter info ...

      pDbBindings[nCol].pwszDataSourceType =
         pwTypeName;                                // derive conversion
      pDbBindings[nCol].pwszName    = NULL;         // won't use param names
      pDbBindings[nCol].ulParamSize = pList->iSizeInBytes;
      pDbBindings[nCol].dwFlags     =
         (pList->eParamIO == EDbInput) ?
          DBPARAMFLAGS_ISINPUT : DBPARAMFLAGS_ISOUTPUT;
      pDbBindings[nCol].bPrecision  = pList->iSize;
      pDbBindings[nCol].bScale      = pList->iDec;

      // free allocated resources as needed ...
      /*
      #ifndef UNICODE
         // TODO: check the release of this allocation somewhere
         //if (pwTypeName)
         //   pParentObj->g_pIMalloc->Free(pwTypeName);
      #endif
       */
   }

   return(iCount);
}  // end of bind fields ...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define the DbParams class

/*
 * Function : DbParams::DbParams(int nParams) : DbFields(nParams)
 * Purpose  : Init the params object.
 * In       : nParams = number of params to allocate (int)
 * Date     : Feb/2k1  (ESob)
 */

DbParams::DbParams(int nParams) : DbFields(nParams)
{  
   Init();
   bWillRepeat = false;
}  // end of init paramters object ...

/*
 * Function : DbParams::Free()
 * Purpose  : Release allocated resources and init object ...
 * Date     : Feb/2k1  (ESob)
 */

void DbParams::Free()
{
   if (pIAccessor) {
      if (hAccessor)
         pIAccessor->ReleaseAccessor(hAccessor,NULL);
      pIAccessor->Release();
   }

   if (pOrdinals)
      delete pOrdinals;
   if (pParamBindings)
      delete pParamBindings;
   if (pFieldBindings)
      delete pFieldBindings;
   if (pStatus)
      delete pStatus;
   if (pValues)
      delete pValues;
   if (pParams)
      delete pParams;

   Init();
}  // end of release allocated resources ...

/*
 * Function : DbParams::~DbParams()
 * Purpose  : Release allocated resources...
 * Date     : Feb/2k1  (ESob)
 */

DbParams::~DbParams()
{
   Free();
}  // end of release allocated resources ...

/*
 * Function : DbParams::Prepare(ICommandText *pICommandText)
 * Purpose  : Prepare parameters and get accessor.
 * In       : pICommandText = command text to set params to (ICommandText*)
 * Date     : Feb/2k1  (ESob)
 * -------- : ------------------------------------------------------------------
 * Updated  : Sep/2k1  (ESob)
 *         -: My first attempt with raw OleDb was done with an ODBC for MS-SQL
 *          : provider. All the code marked as 'NOT NEEDED' below works fine
 *          : until I tried to run it with the OleDb for MS-SQL provider. It
 *          : complanied and give me different error messages related to the
 *          : accessor. After some additional reading I find out that the
 *          : DBPARAMBINDINFO need not to be set since it is eventually called
 *          : by the provider as explained below. I didn't remove the previous
 *          : code so you can see what it needs to be done when you are using
 *          : DBPARAMBINDINFO. This code does have problems when using the
 *          : OleDb for MS-SQL provider. Just try it your self. For your
 *          : convinience I included a macro 'USE_DBPARAMBINDINFO_' so you
 *          : only need to define it to include the code.
 * -------- : ------------------------------------------------------------------
 */

// #define USE_DBPARAMBINDINFO_  // read 'Sep/2k1' update notes above

bool DbParams::Prepare(ICommandText *pICommandText)
{  bool isok = false;

   /*
    * START **** NOT NEEDED ****
    * No need to describe command parameters (parameter name, data type
    * etc) in DBPARAMBINDINFO structure and then SetParameterInfo(). The
    * provider obtains this information by calling appropriate helper
    * function.
    */

   #ifdef USE_DBPARAMBINDINFO_
      // prepare parameter bindings ...

      pOrdinals = new DB_UPARAMS[iCount];
      pParamBindings = new DBPARAMBINDINFO[iCount];

      // make sure that we have the alloc resources ...

      if ((!pOrdinals) || (!pParamBindings)) {
         if (pOrdinals)
            delete pOrdinals;
         if (pParamBindings)
            delete pParamBindings;

         pOrdinals = NULL;
         pParamBindings = NULL;

         // TODO: through an exception here ...
         goto DbParamsPrepareCleanUp;
      }

      // setup ordinals ...

      UINT count;
      for (count = 0; count < iCount; count++)
         *(pOrdinals+count) = count+1;

      // setup parameter bindings ...

      BindParams();
   #endif

   /*
    * END   **** NOT NEEDED ****
    */

   // set parameter bindings information.

   ICommandWithParameters *pICmdWithParams;
   pICommandText->QueryInterface(IID_ICommandWithParameters, 
      (void**)&pICmdWithParams);

   if (pICmdWithParams) {

      /*
       * START **** NOT NEEDED ****
       * See previous 'NOT NEEDED' section above.
       */

      #ifdef USE_DBPARAMBINDINFO_
         pICmdWithParams->SetParameterInfo(
            iCount,
            pOrdinals, 
            pParamBindings);
      #endif
      pICmdWithParams->Release();

      /*
       * END   **** NOT NEEDED ****
       */

      // prepare the command for repeated execution if needed ...

      if (bWillRepeat) {
         ICommandPrepare *pICommandPrepare;
         pICommandText->QueryInterface(IID_ICommandPrepare, 
            (void**)&pICommandPrepare);

         if (pICommandPrepare) {
            hrLastError = pICommandPrepare->Prepare(0);
            pICommandPrepare->Release();
         }
      }
      else
         hrLastError = S_OK;

      if (hrLastError == S_OK) {

         // create parameter accessor field bindings and binding
         // fields status vectors ...

         pFieldBindings = new DBBINDING[iCount];
         pStatus        = new DBBINDSTATUS[iCount];

         if ((!pFieldBindings) || (!pStatus)) {
            if (pFieldBindings)
               delete pFieldBindings;
            if (pStatus)
               delete pStatus;

            pFieldBindings = NULL;
            pStatus = NULL;

            // TODO: through an exception here ...
            goto DbParamsPrepareCleanUp;
         }

         // accessor bind parameter fields ...

         ulParamRecLen = BindFields(pFieldBindings);

         pICommandText->QueryInterface(IID_IAccessor, (void**)&pIAccessor);
         if (pIAccessor) {

            // get accessor that will be used to specify parameter data

            hrLastError = pIAccessor->CreateAccessor(
               DBACCESSOR_PARAMETERDATA, // setup to get needed accessor
               iCount,                   // Num of parameters being bound
               pFieldBindings,           // Structure containing bind info
               ulParamRecLen,            // Size of parameter structure
               &hAccessor,               // Returned accessor handle
               pStatus                   // Info about binding validity
            );

            isok = (hrLastError == S_OK);
            if (pIAccessor)
               pIAccessor->Release();
         }
      }
   }

   // prepare parameters structure to be used for execution ...

   if (isok) {
      pParams = new DBPARAMS[1];
      if (pParams != NULL) {
         _CHAR *pRowBuff = pRowBuffer;
         if (!pRowBuff)
            pRowBuff = pValues = new _CHAR[ulParamRecLen];
         if (pValues)
         {
            // clear value...
            _CharClearString(pValues,ulParamRecLen);

            pParams->pData      = pRowBuff;  // pData is the buffer pointer
            pParams->cParamSets = 1;         // Number of sets of parameters
            pParams->hAccessor  = hAccessor; // Accessor to the parameters
         }
         else
            isok = false;
      }
      else
         isok = false;
   }

   // clean up resources as needed ...

   DbParamsPrepareCleanUp : {
   }

   return(isok);
}  // end of prepare parameters ...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define the DbFastLoad class

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define the ClConnection class

/*
 * Function : HRESULT DbConnection::SetFastLoadProperty(bool fSet)
 * Purpose  : Set the SQLOLEDB provider-specific data source property
 *          : SSPROP_ENABLEFASTLOAD to VARIANT_TRUE. With this property set to
 *          : VARIANT_TRUE, the newly created session allows the consumer access
 *          : to IRowsetFastLoad. Returns
 * In       : fSet = 'true' to set fast load property, 'false' to reset it.
 * Date     : Jan/2k2  (ESob)
 */

HRESULT DbConnection::SetFastLoadProperty(bool fSet)
{
   HRESULT        hr = S_OK;
   IDBProperties *pIDBProps = NULL;
   DBPROP         rgProps[1];
   DBPROPSET      PropSet;

   VariantInit(&rgProps[0].vValue);

   rgProps[0].dwOptions    = DBPROPOPTIONS_REQUIRED;
   rgProps[0].colid        = DB_NULLID;
   rgProps[0].vValue.vt    = VT_BOOL;
   rgProps[0].dwPropertyID = SSPROP_ENABLEFASTLOAD;

   if (fSet)
       rgProps[0].vValue.boolVal = VARIANT_TRUE;
   else
       rgProps[0].vValue.boolVal = VARIANT_FALSE;

   PropSet.rgProperties    = rgProps;
   PropSet.cProperties     = 1;
   PropSet.guidPropertySet = DBPROPSET_SQLSERVERDATASOURCE;

   hr = pIDBInitialize->QueryInterface(
      IID_IDBProperties, (LPVOID *)&pIDBProps);

   if (SUCCEEDED(hr)) {
      hr = pIDBProps->SetProperties(1, &PropSet);
   }

   VariantClear(&rgProps[0].vValue); 

   if (pIDBProps)
      pIDBProps->Release();

   return hr;
}  // end of set fast load property

/// <summary>Set Comamnd properties</summary>
/// <param name="pICommandText">Command Text to set properties of</param>
bool DbConnection::SetCommandProperties(ICommandText *pICommandText)
{
   ICommandProperties *pICommandProperties;
   hrLastHR = pICommandText->QueryInterface(IID_ICommandProperties,
      (void**) &pICommandProperties);
   if (hrLastHR != S_OK)
      return(false);

   DBPROP    properties[1];
   DBPROPSET rgPropertySets[1];

	VariantInit(&properties[0].vValue);

   properties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
   properties[0].colid = DB_NULLID;
   properties[0].dwPropertyID = DBPROP_SKIPROWCOUNTRESULTS;
   properties[0].vValue.vt = VT_BOOL;
   properties[0].vValue.boolVal = VARIANT_FALSE;

   rgPropertySets[0].rgProperties    = properties;
   rgPropertySets[0].cProperties     = 1;
   rgPropertySets[0].guidPropertySet = DBPROPSET_ROWSET;

   pICommandProperties->SetProperties(1,rgPropertySets);
   pICommandProperties->Release();
   pICommandProperties = NULL;

   return(true);
}  // end of SetCommandProperties

/*
 * Function : ICommandText *DbConnection::GetICommandText()
 * Purpose  : Get command text interface ... Caller is responsable for releasing
 *          : this pointer as ...
 *          :
 *          :    ICommandText *pCText = GetICommandText();
 *          :    // do something with the pCText ...
 *          :    pCText->Release();
 *          :
 * Date     : Feb/2k1  (ESob)
 */

ICommandText *DbConnection::GetICommandText()
{  ICommandText *pICommandText;

   // Get the DB session object.

   if (!pIDbCreateSession) {
      hrLastHR = pIDBInitialize->QueryInterface(IID_IDBCreateSession,
         (void**) &pIDbCreateSession);
      if (hrLastHR != S_OK)
         return(NULL);
   }

   // Create the session, getting an interface for command creation.

   if (!pIDbCreateCommand) {
      hrLastHR = pIDbCreateSession->CreateSession(NULL, IID_IDBCreateCommand,
         (IUnknown**) &pIDbCreateCommand);
      if (hrLastHR != S_OK)
         return(NULL);
   }

   // Create the command object.

   hrLastHR = pIDbCreateCommand->CreateCommand(NULL, IID_ICommandText,
      (IUnknown**) &pICommandText);
   if (hrLastHR != S_OK)
      return(NULL);

   /*
   if (!pICommand) {
      hrLastHR = pIDbCreateCommand->CreateCommand(NULL, IID_ICommand,
         (IUnknown**) &pICommand);
      if (hrLastHR != S_OK)
         return(NULL);
   }

   hrLastHR = pICommand->QueryInterface(
             IID_ICommandText, (void**)&pICommandText);
   if (hrLastHR != S_OK)
      return(NULL);
    */

   return(pICommandText);
}  // end of get ICommandText ...

/*
 * Function : DbConnection::DbConnection()
 * Purpose  : Initialze the OLEDB object.
 * Date     : Feb/2k1  (ESob)
 */

DbConnection::DbConnection()
{  pIDBInitialize = NULL;
   g_pIMalloc     = NULL;

   // CoGetMalloc(MEMCTX_TASK, &g_pIMalloc);

   pIDbCreateSession = NULL;
   pIDbCreateCommand = NULL;
   pICommand         = NULL;

   bUseODBC = false;
}  // end of init ole db object

/*
 * Function : DbConnection::~DbConnection()
 * Purpose  : Free allocated resources .
 * Date     : Feb/2k1  (ESob)
 */

DbConnection::~DbConnection()
{
   Disconnect();
}  // end of free allocated resources ...

/*
 * Function : bool DbConnection::Disconnect()
 * Purpose  : Disconnect and release allocated resources ...
 * Date     : Feb/2k1  (ESob)
 */

bool DbConnection::Disconnect()
{
   if (pICommand)
      pICommand->Release();
   if (pIDbCreateSession)
      pIDbCreateSession->Release();
   if (pIDbCreateCommand)
      pIDbCreateCommand->Release();

   pICommand = NULL;
   pIDbCreateSession = NULL;
   pIDbCreateCommand = NULL;

   if (pIDBInitialize != NULL) {
      if (pIDBInitialize->Uninitialize() != S_OK) {

         // Uninitialize is not required, but it will fail if an 
         // interface has not been released. We can use it for 
         // debugging.

         // TODO : Through an exception here ...
      }
      pIDBInitialize->Release();
   }

   // release allocator

   if (g_pIMalloc != NULL)
      g_pIMalloc->Release();

   pIDBInitialize = NULL;
   g_pIMalloc     = NULL;

   return(true);
}  // end of disconnect ...

/*
 * Function : bool DbConnection::CheckError(HRESULT hrLastErr)
 * Purpose  : Check last error. 'true' is returned if all is ok. If there
 *          : any default print function, this procedure will try to
 *          : execute it if it finds one and there was an error.
 * In       : hrLastErr = last error (HRESULT)
 * Date     : Feb/2k1  (ESob)
 */

bool DbConnection::CheckError(HRESULT hrLastErr)
{
   bool isok = true;
   if (hrLastErr != S_OK)
   {
      Kif::Win32::String mess;

      IErrorInfo    *pIErrorInfo    = NULL;
      IErrorRecords *pIErrorRecords = NULL;

      HRESULT hr = GetErrorInfo(0,&pIErrorInfo);

      if ((hr == S_OK) && (pIErrorInfo))
      {

         // display erro info ...

         BSTR bstrDescription = NULL;
         BSTR bstrSource      = NULL;

         // Get the description of the error.

         hr   = pIErrorInfo->GetDescription(&bstrDescription);
         isok = (hr == S_OK);

         if (isok) {

            // Get the source of the error -- this will be the window title.

            hr   = pIErrorInfo->GetSource(&bstrSource);
            isok = (hr == S_OK);

         }

         // get error message ...

         if (isok) {

            _bstr_t bDescript(bstrDescription);
            _bstr_t bSource(bstrSource);

            // massage string of chars to convert from BSTR to
            // UNICODE or TCHAR as needed ...

            #ifdef UNICODE

               TCHAR *pDescript = (wchar_t*)bDescript;
               TCHAR *pSource   = (wchar_t*)bSource;

            #else

               TCHAR *pDescript = (TCHAR*)bDescript;
               TCHAR *pSource   = (TCHAR*)bSource;

            #endif

            // prepare message ...
            mess.Concat(TEXT("DBError: ("));
            mess.Concat(pDescript);
            mess.Concat(TEXT(") Source ("));
            mess.Concat(pSource);
            mess.Concat(TEXT(") HResult ("));
            mess.Concat(((long)hrLastErr));
            mess.Concat(TEXT(")"));
         }
         else
         {
            mess.Concat(TEXT("GetDescription and/or GetSource failed! ("));
            mess.Concat((long)hr);
            mess.Concat(TEXT(")"));
         }

         // clean up allocated resources ...

         if (bstrSource)
            SysFreeString(bstrSource);
         if (bstrDescription)
            SysFreeString(bstrDescription);

         isok = false;

      }
      else
      {
         mess.Concat(TEXT("GetErrorInfo failed! ("));
         mess.Concat((long)hr);
         mess.Concat(TEXT(")"));
         isok = false;
      }

      // print error message ...

      if (!isok)
         Kif::Win32::Diagnostics::Log::ConsoleWriteMessage(mess.GetBuffer());
   }

   return(isok);
}  // end of check given error ...

/*
 * Function : bool DbConnection::InitDSO(
 *          :    TCHAR *pDSN,TCHAR *pUserId,TCHAR *pPasswd)
 * Purpose  : Init data source object ... The 'pIDBInitialize' pointer is
 *          : initialized with given provider ...
 * In       : pDSN     = data source name (TCHAR*)
 *          : pCatalog = database (TCHAR*)
 *          : pUserId  = user id (TCHAR*)
 *          : pPasswd  = password (TCHAR*)
 * Date     : Feb/2k1  (ESob)
 */

bool DbConnection::InitDSO(TCHAR *pDSN,TCHAR *pCatalog,
     TCHAR *pUserId,TCHAR *pPasswd, DbDataSourceObject Dso)
{
   // make sure that we have allocated the memory that we need to
   // prepare and manage commands ...

   if (!g_pIMalloc)
      CoGetMalloc(MEMCTX_TASK, &g_pIMalloc);

   // set provider to use

   if (!bUseODBC)
   {
      if (Dso == DbDso_NativeSqlClient)
      {
         // 06-11-04 Create an instance of the SQL OleDb provider
         #ifdef USE_MSSQL_2000
         CoCreateInstance(CLSID_SQLOLEDB, NULL, CLSCTX_INPROC_SERVER,
            IID_IDBInitialize, (void**)&pIDBInitialize);
         #else
         // 06-11-04 Support for MS-SQL 2005 Native Client
         CoCreateInstance(CLSID_SQLNCLI, NULL, CLSCTX_INPROC_SERVER,
            IID_IDBInitialize, (void**)&pIDBInitialize);
         #endif
      }
      #ifdef ORAOLEDB
      else
      {
         CoCreateInstance(CLSID_OraOLEDB, NULL, CLSCTX_INPROC_SERVER,
            IID_IDBInitialize, (void**)&pIDBInitialize);
      }
      #endif
   }
   else
      // Create an instance of the MSDASQL (ODBC) provider
      CoCreateInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER,
         IID_IDBInitialize, (void**)&pIDBInitialize);

   if (pIDBInitialize == NULL)
      return(false);

   // See "Getting and Setting Properties."

   if (!SetInitProps(pDSN,pCatalog,pUserId,pPasswd))
      return(false);

   hrLastHR = (pIDBInitialize)->Initialize();
   if (!OLEDB_SUCCESS(hrLastHR)) {
      CheckError(hrLastHR);
      // TODO : Throw an exception here ...
      return(false);
   }

   return (true);
}  // end of init DSO

/*
 * Function : bool DbConnection::SetInitProps(
 *          :    TCHAR *pDSN,TCHAR *pCatalog,TCHAR *pUserId,TCHAR *pPasswd)
 * Purpose  : Initialize properties ... 'true' is returned if initialization
 *          : succeeded.
 * In       : pDSN     = data source name (TCHAR*)
 *          : pCatalog = database (TCHAR*)
 *          : pUserId  = user id (TCHAR*)
 *          : pPasswd  = password (TCHAR*)
 * Date     : Feb/2k1  (ESob)
 */

bool DbConnection::SetInitProps(TCHAR *pDSN,TCHAR *pCatalog,
     TCHAR *pUserId,TCHAR *pPasswd)
{
   const ULONG     nProps = 5;
   IDBProperties*  pIDBProperties;
   DBPROP          InitProperties[nProps];
   DBPROPSET       rgInitPropSet;
   ULONG           tProps = 0;

   // if we are not using UNICODE, then we most convert the input strings into
   // UNICODE conterparts ...

   #ifdef UNICODE
      TCHAR *pToDSN     = pDSN;
      TCHAR *pToUserId  = pUserId;
      TCHAR *pToPasswd  = pPasswd;
      TCHAR *pToCatalog = pCatalog;
   #else

      wchar_t pToDSN[MaxStringLength+1];
      wchar_t pToUserId[MaxStringLength+1];
      wchar_t pToPasswd[MaxStringLength+1];
      wchar_t pToCatalog[MaxStringLength+1];

      Kif::Win32::String::Copy(pToDSN,MaxStringLength,pDSN);
      Kif::Win32::String::Copy(pToUserId,MaxStringLength,pUserId);
      Kif::Win32::String::Copy(pToPasswd,MaxStringLength,pPasswd);
      if (pCatalog)
         Kif::Win32::String::Copy(pToCatalog,MaxStringLength,pCatalog);

   #endif

   // Initialize common property options.

   ULONG count;
   for (count = 0; count < nProps; count++ )
   {
      VariantInit(&InitProperties[count].vValue);
      InitProperties[count].dwOptions = DBPROPOPTIONS_REQUIRED;
      InitProperties[count].colid = DB_NULLID;
   }

   // Level of prompting that will be done to complete the
   // connection process

   InitProperties[tProps].dwPropertyID   = DBPROP_INIT_PROMPT;
   InitProperties[tProps].vValue.vt      = VT_I2;
   InitProperties[tProps].vValue.iVal    = DBPROMPT_NOPROMPT;
   tProps++;

   // Data source name ...

   InitProperties[tProps].dwPropertyID   = DBPROP_INIT_DATASOURCE;
   InitProperties[tProps].vValue.vt      = VT_BSTR;
   InitProperties[tProps].vValue.bstrVal = SysAllocString(pToDSN);
   tProps++;

   // catalog

   if (pCatalog) {
      InitProperties[tProps].dwPropertyID  = DBPROP_INIT_CATALOG;
      InitProperties[tProps].vValue.vt     = VT_BSTR;
      InitProperties[tProps].vValue.bstrVal= SysAllocString(pToCatalog);
      tProps++;
   }

   // User ID

   InitProperties[tProps].dwPropertyID   = DBPROP_AUTH_USERID;
   InitProperties[tProps].vValue.vt      = VT_BSTR;
   InitProperties[tProps].vValue.bstrVal = SysAllocString(pToUserId);
   tProps++;

   // Password

   InitProperties[tProps].dwPropertyID   = DBPROP_AUTH_PASSWORD;
   InitProperties[tProps].vValue.vt      = VT_BSTR;
   InitProperties[tProps].vValue.bstrVal = SysAllocString(pToPasswd);
   tProps++;

   rgInitPropSet.guidPropertySet    = DBPROPSET_DBINIT;
   rgInitPropSet.cProperties        = tProps;
   rgInitPropSet.rgProperties       = InitProperties;

   // Set initialization properties.

   pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)
      &pIDBProperties);
   hrLastHR = pIDBProperties->SetProperties(1, &rgInitPropSet);

   for (count = 0; count < tProps; count++) {
      if (InitProperties[count].vValue.vt == VT_BSTR)
         SysFreeString(InitProperties[count].vValue.bstrVal);
   }

   pIDBProperties->Release();

   if (!OLEDB_SUCCESS(hrLastHR)) {
      // TODO : Throw an exception here ...
      return(false);
   }

   return (true);
}  // end of set init properties ...

/*
 * Function : DbFastLoad *DbConnection::PrepareForFastLoad(
 *          :    TCHAR *pTableName,DbFields *pFields,)
 * Purpose  : Prepare for fast load (bulk copy).
 * In       : pTableName = pointer to table name (TCHAR*)
 *          : pFields    = optional pointer to result fields (DbFields*)
 * Date     : Feb/2k1  (ESob)
 */

DbFastLoad *DbConnection::PrepareForFastLoad(
   TCHAR *pTableName, DbFields *pFields)
{  bool done = false;

   // create resulting DbFastLoad object

   DbFastLoad *pFL = new DbFastLoad();
   if (!pFL)
      return(NULL);
   pFL->pConnection = this;

   // first we need to identify target table for fast load.

   DBID TableID;
   size_t nlen = Kif::Win32::String::GetLength(pTableName);
   TableID.eKind = DBKIND_NAME;
   TableID.uName.pwszName = new WCHAR[nlen+2];

   Kif::Win32::String::Copy(TableID.uName.pwszName,nlen,pTableName);

   if (!TableID.uName.pwszName)
      goto ReleaseAllocatedResources;

   // Get the fastload pointer

   HRESULT hr;

   if (FAILED(hr = SetFastLoadProperty(TRUE)))
      goto ReleaseAllocatedResources;

   if (FAILED(hr = pIDBInitialize->QueryInterface(
      IID_IDBCreateSession,(void **) &pFL->pIDbCreateSession)))
      goto ReleaseAllocatedResources;

   if (FAILED(hr = pFL->pIDbCreateSession->CreateSession( 
      NULL,IID_IOpenRowset,(IUnknown **) &pFL->pIOpenRowsetFL)))
      goto ReleaseAllocatedResources;

   // Get IRowsetFastLoad initialized to use the given table

   if (FAILED(hr = pFL->pIOpenRowsetFL->OpenRowset(
      NULL,                     // control IUnknown rset is to be aggregated
      &TableID,                 // id of table to open
      NULL,                     // id of index to open
      IID_IRowsetFastLoad,      // IID of requested resource
      0,                        // number of properties (DBPROPSET)
      NULL,                     // pointer to array of property set/s
      (LPUNKNOWN *)&pFL->pIFastLoad)))  // returned interface pointer
      goto ReleaseAllocatedResources;

   // bind fields

   pFL->pDbBindings = new DBBINDING[pFields->iCount];
   if (!pFL->pDbBindings)
      goto ReleaseAllocatedResources;
   pFL->ulRowLen = pFields->BindFields(pFL->pDbBindings);

   pFL->pDbBindStatus = new DBBINDSTATUS[pFields->iCount];
   if (!pFL->pDbBindStatus)
      goto ReleaseAllocatedResources;

   // next setup an accessor for the data

   if (FAILED(hr = pFL->pIFastLoad->QueryInterface(
      IID_IAccessor, (void **) &pFL->pIAccessor)))
      goto ReleaseAllocatedResources;

   if (FAILED(hr = pFL->pIAccessor->CreateAccessor(
      DBACCESSOR_ROWDATA,       // Accessor will be used to retrieve row data
      pFields->iCount,          // Number of columns being bound
      pFL->pDbBindings,         // Structure containing bind info
      pFL->ulRowLen,            // Not used for row accessors
      &pFL->hAccessor,          // Returned accessor handle
      pFL->pDbBindStatus)))     // Information about binding validity
      goto ReleaseAllocatedResources;

   // next setup data buffer. you must init (memset) the 'pRowValues' else
   // it is must likely that when you insert a record E_FAIL will be returned
   // an error due to the fact that garbage in the uninit memory buffer can't
   // be converted to base type ...

   if (pFL->pRowValues)
      delete pFL->pRowValues;
   pFL->pRowValues = new _CHAR[pFL->ulRowLen];
   if (pFL->pRowValues)
      _CharClearString(pFL->pRowValues,pFL->ulRowLen);

   pFL->pFields = pFields;
   done = true;

   // release allocated resources

   ReleaseAllocatedResources:

   if (!done) {
      delete pFL;
      pFL = NULL;
   }

   if (TableID.uName.pwszName)
      delete []TableID.uName.pwszName;

   return(pFL);
}  // end of prepare for fast load

/*
 * Function : DbResult *DbConnection::Prepare(
 *          :    TCHAR *pQueryStr,DbFields *pFields,DbParams *pParams,
 *          :    bool bToRepeat)
 * Purpose  : Execute given query. If command and result object was prepared
 *          : with no error, this function will return a DbResult object.
 * In       : pQueryStr = pointer to query string (TCHAR*)
 *          : pFields   = optional pointer to result fields (DbFields*)
 *          : pParams   = optional pointer to parameters (DbParams*)
 *          : bToRepeat = 'true' to prepare for repeating execution (bool)
 * Date     : Feb/2k1  (ESob)
 */

DbResult *DbConnection::Prepare(TCHAR *pQueryStr,
   DbFields *pFields,DbParams *pParams,bool bToRepeat)
{  ICommandText *pICommandText = NULL;
   DbResult   *pResult       = NULL;

   // Get command text interface pointer ...

   pICommandText = GetICommandText();
   if (!pICommandText)
      return(NULL);

   // convert string into UNICODE if needed ...

   #ifdef UNICODE
      TCHAR *pwQuery = pQueryStr;
   #else

      size_t slen = Kif::Win32::String::Length(pQueryStr);
      wchar_t *pwQuery = (wchar_t*)g_pIMalloc->Alloc(sizeof(wchar_t)*(slen+1));

      if (pwQuery)
         Kif::Win32::String::Copy(pwQuery,slen,pQueryStr);
      else {
         // TODO : Trough an exception here
         goto DbConnection_Execute_CleanUp;
      }

   #endif

   // The command requires the actual text as well as an indicator
   // of its language and dialect.

   pICommandText->SetCommandText(DBGUID_DBSQL, pwQuery);
   SetCommandProperties(pICommandText);

   // Prepare parameter accessor ...

   if (pParams) {
      pParams->bWillRepeat = bToRepeat;

      // make sure that we have set the parameters parent object ...

      pParams->DbFields::Set(this);

      // prepare parameters ...

      if (!pParams->Prepare(pICommandText))
      {
         CheckError(pParams->hrLastError);
         goto DbConnection_Execute_CleanUp;
      }
      else
      {
         // copy values to parameter storage ...
         pParams->SetValues();
      }
   }

   // prepare the result object if all is ok, else set result as NULL ...

   pResult = new DbResult();

   if (pResult) {
      pResult->pParams = pParams;
      pResult->pFields = pFields;
      pResult->SetCommandText(pICommandText);
      pResult->SetParent(this);
   }

   // clean up by releasing allocated resources ...

   DbConnection_Execute_CleanUp : {

      // free allocated resources as needed ...

      #ifndef UNICODE
         if (pwQuery)
            g_pIMalloc->Free(pwQuery);
      #endif
   }

   return(pResult);
}  // end of execute given query ...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define the DbResult class

/*
 * Function : DbResult::DbResult()
 * Purpose  : Initialize the result from query ...
 * Date     : Feb/2k1  (ESob)
 */

DbResult::DbResult()
{
   ResetResources();
}  // end of init the result object ...

/*
 * Function : DbResult::ResetResources()
 * Purpose  : Reset all resources to NULL and / or undefined ...
 * Date     : Feb/2k1  (ESob)
 */

void DbResult::ResetResources()
{  pIRowset       = NULL;
   hAccessor      = NULL;
   prghRows       = NULL;
   pDbBindings    = NULL;
   pDbBindStatus  = NULL;
   pColumnsInfo   = NULL;
   pColumnStrings = NULL;
   pIAccessor     = NULL;
   pICommandText  = NULL;
   pRowValues     = NULL;
   cRowsObtained  = -1;
   pFields        = NULL;
   ulRowLen       = 0;
   pIMultipleResults = NULL;
   nCols          = 0;
   eLastResultStatus = EDbNoResult;
}  // end of reset resources ...

/*
 * Function : DbResult::~DbResult()
 * Purpose  : Release allocated resources ...
 * Date     : Feb/2k1  (ESob)
 */

DbResult::~DbResult()
{
   if (pRowValues)
      delete pRowValues;
   if (pIRowset != NULL)
      pIRowset->Release();
   if (pIMultipleResults != NULL)
      pIMultipleResults->Release();

}  // end of release allocated resources ..

/*
 * Function : bool DbResult::Execute()
 * Purpose  : Execute query ... 'true' is returned if all is ok, else the
 *          : error code is stored in hrLastError.
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::Execute()
{

   // Execute the command.

   eLastResultStatus = EDbNoResult;
   if (pFields && (!pParams)) {
      hrLastError = pICommandText->Execute(NULL, IID_IRowset,
         pParams ? pParams->pParams : NULL,
         &lAffectedRows, (IUnknown**) &pIRowset);
   }
   else {
      hrLastError = pICommandText->Execute(NULL, IID_IMultipleResults,
         pParams ? pParams->pParams : NULL,
         &lAffectedRows, (IUnknown**) &pIMultipleResults);
   }

   if (hrLastError != S_OK) {

      // check statuses ...
      // DBCOLUMNINFO colinfo;
      // DbColumnInfo cinfo1((char*)pParams->pParams->pData+8,
      //   pParams->pFieldBindings,&colinfo);

      // display error if needed/possible
      pParentObj->CheckError(hrLastError);
   }

   return(hrLastError == S_OK);
}  // end of prepare query ...

/*
 * Function : DbResult::SetRowset(IRowset *pRSet)
 * Purpose  : Set the result row set. If there is another release the privious
 *          : then set the new one.
 * Date     : Feb/2k1  (ESob)
 */

void DbResult::SetRowset(IRowset *pRSet)
{
   if (pIRowset != NULL)
      pIRowset->Release();

   pIRowset = pRSet;

   nCols = 0;
   pColumnsInfo   = NULL;
   pColumnStrings = NULL;
}  // end of set row set ...

/*
 * Function : DbConnection::GetColumnsInfo()
 * Purpose  : Get columns info.
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::GetColumnsInfo()
{  IColumnsInfo *pIColumnsInfo;

   // contemplate that we don't have a record set therefore we will not be
   // able to query columns info, therefore return false meaning that we
   // can't gather requested info

   pColumnStrings = NULL;
   if (!pIRowset)
      return (false);

   // we do have a record set, therefore get columns info ...

   pParentObj->hrLastHR = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)
      &pIColumnsInfo);
   if (pParentObj->hrLastHR != S_OK)
      return(false);

   pParentObj->hrLastHR = pIColumnsInfo->GetColumnInfo(&nCols, &pColumnsInfo,
      &pColumnStrings);

   if (pParentObj->hrLastHR != S_OK)
      nCols = 0;

   pIColumnsInfo->Release();
   return (pParentObj->hrLastHR == S_OK);
}  // end of get columns info ...

/*
 * Function : bool DbResult::CreateBindings(DbFields *pFields)
 * Purpose  : Create column and status bindings from given field list ...
 * In       : pFields = field list to prepare bindings from (DbFields*)
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::CreateBindings(DbFields *pFields)
{  ulRowLen   = 0;

   // make sure that if we had a 'pRowValues' buffer it get's released first

   if (pRowValues)
      delete pRowValues;
   pRowValues = NULL;

   // try creating bindings ...

   nCols = pFields->iCount;

   pDbBindings = new DBBINDING[nCols];
   if (pDbBindings)
   {
      ulRowLen   = pFields->BindFields(pDbBindings);
      pRowValues = new _CHAR[ulRowLen];

      // DBBindStatus hold info about binding status ...

      if (pRowValues)
      {
         _CharClearString(pRowValues,ulRowLen);
         pDbBindStatus = new DBBINDSTATUS[nCols];
      }
   }

   // if we got some memory allocation problem, then release those that
   // where allocated ...

   bool isok = (pRowValues != NULL);
   if (!isok) {
      if (pDbBindings)
         delete pDbBindings;

      pDbBindings = NULL;
   }

   return(isok);;
}  // end of column bindings ...

/*
 * Function : bool DbResult::CreateBindings()
 * Purpose  : Create column and status bindings from fetched column info
 *          : queried to database ...
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::CreateBindings()
{  ULONG nCol;
   ULONG ulColLen;

   ulRowLen   = 0;

   // make sure that we release 'pRowValues' first if we have one

   if (pRowValues)
      delete pRowValues;
   pRowValues = NULL;

   // try creating bindings ...

   pDbBindings = new DBBINDING[nCols];
   if (pDbBindings) {
      for (nCol = 0; nCol < nCols; nCol++) {

         ulColLen = pColumnsInfo[nCol].ulColumnSize;

         pDbBindings[nCol].iOrdinal   = nCol+1;
         pDbBindings[nCol].obLength   = 0;
         pDbBindings[nCol].obStatus   = ulRowLen;
         pDbBindings[nCol].obValue    = ulRowLen+sizeof(DBSTATUS);
         pDbBindings[nCol].pTypeInfo  = NULL;
         pDbBindings[nCol].pObject    = NULL;
         pDbBindings[nCol].pBindExt   = NULL;
         pDbBindings[nCol].dwPart     = DBPART_STATUS|DBPART_VALUE;
         pDbBindings[nCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
         pDbBindings[nCol].eParamIO   = DBPARAMIO_NOTPARAM;
         pDbBindings[nCol].cbMaxLen   = ulColLen;
         pDbBindings[nCol].dwFlags    = pColumnsInfo[nCol].dwFlags;
         pDbBindings[nCol].wType      = pColumnsInfo[nCol].wType;
         pDbBindings[nCol].bPrecision = pColumnsInfo[nCol].bPrecision;
         pDbBindings[nCol].bScale     = pColumnsInfo[nCol].bScale;

         // call parent bind field function to make sure we can manage
         // special cases ...

         if (pParentObj)
            ulColLen = pParentObj->BindField(pDbBindings+nCol);

         // calculate start address for next value buffer

         ulRowLen += ulColLen+sizeof(DBSTATUS);

         // align memory ...

         ulRowLen  = ROUNDUP(ulRowLen);
      }

      pRowValues = new _CHAR[ulRowLen];
      _CharClearString(pRowValues,ulRowLen);

      // DBBindStatus hold info about binding status ...

      if (pRowValues)
         pDbBindStatus = new DBBINDSTATUS[nCols];
   }

   // if we got some memory allocation problem, then release those that
   // where allocated ...

   bool isok = (pDbBindings && pRowValues && pDbBindStatus);
   if (!isok) {
      if (pDbBindings)
         delete pDbBindings;
      if (pRowValues)
         delete pRowValues;

      pDbBindings = NULL;
      pRowValues  = NULL;
   }

   return(isok);;
}  // end of column bindings ...

/*
 * Function : bool DbResult::GetAccessor()
 * Purpose  : Get the accessor ...
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::GetAccessor()
{
   bool isok;

   // if field list has been given, then we don't need to fetch result
   // set from provider, just bind using given information ...

   if (pFields)
      isok = CreateBindings(pFields);
   else {

      // if we got here, we most fetch result column info from provider

      isok = GetColumnsInfo();
      if (isok)
         isok = CreateBindings();
   }

   if (isok) {

      // Create the accessor (see comments in the header file)

      pIRowset->QueryInterface(IID_IAccessor, (void**) &pIAccessor);

      // although 'ulRowLen' is not used for fetching row data I will include
      // it any away ...

      pParentObj->hrLastHR = pIAccessor->CreateAccessor(
         DBACCESSOR_ROWDATA, // Accessor will be used to retrieve row data
         nCols,              // Number of columns being bound
         pDbBindings,        // Structure containing bind info
         ulRowLen,           // Not used for row accessors
         &hAccessor,         // Returned accessor handle
         pDbBindStatus       // Information about binding validity
      );
   }

   return(pIAccessor != NULL);
}  // end of get accessor ...

/*
 * Function : bool DbResult::Open(DbFields *pInFields,int iNumRows)
 * Purpose  : Open row set fetching given number of rows.
 * In       : pInFields = pointer to fields (DbFields*)
 *          : iNumRows  = requested number of rows (int)
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::Open(DbFields *pInFields,int iNumRows)
{
   // make sure that we have some result fields pointers and that
   // the parent object is inited as required ...

   if (pInFields)
      pFields = pInFields;

   if (pFields)
      pFields->Set(pParentObj);

   // get accessor ...

   bool isok = GetAccessor();

   // get accessor ...

   if (isok) {

      // allocate row handles for given number of rows ...

      prghRows = new HROW[iNumRows];
      isok = (prghRows != NULL);
   }

   if (isok) {
      prghRows[0]    = NULL;
      cRowsObtained  = 0;
      cRowsRequested = iNumRows;
      cCurrentRow    = 0;
   }

   return(isok);
}  // end of open row set to start fetching ...

/*
 * Function : bool DbResult::Next(DbFields *pFields)
 * Purpose  : Fetch next row of data.
 * In       : pFields = pointer to fields to stuff info into (DbFields *)
 * Date     : Feb/2k1  (ESob)
 */

bool DbResult::Next(DbFields *pFields)
{  
   // make sure that we do have a record set, if not then return false as
   // if we have finished ...

   if (!pIRowset)
      return(false);

   // fetch more rows if needed ...

   if (cRowsObtained <= cCurrentRow) {

      // release previous allocated row handles ...

      if (*prghRows)
         pIRowset->ReleaseRows(cRowsObtained, prghRows, NULL, NULL, NULL);

      // get more rows ...

      HROW *pRows = prghRows;    // pointer to the row handle

      cCurrentRow = 0;
      pIRowset->GetNextRows(
         0,                      // reserved
         0,                      // rows to skip
         cRowsRequested,         // requested number of rows
        &cRowsObtained,          // number of obtained rows
        &pRows);                 // filled in w/ row handles.

      // All done; there are no more rows left to get.

      if (cRowsObtained == 0) {
         prghRows[0]   = NULL;
         cRowsObtained = 0;
         return(false);
      }

   }

   // get current row set, first get the row buffer to be used, by
   // default we will asume that it was allocated here, else it was
   // specified in earlier and we are probably mapping values directly

   _CHAR *pRowBuffer = pRowValues;
   if (pFields)
      if (pFields->pRowBuffer)
         pRowBuffer = pFields->pRowBuffer;

   pParentObj->hrLastHR = pIRowset->GetData(
      prghRows[cCurrentRow], hAccessor, pRowBuffer);
   cCurrentRow++;

   // if we have the fields list, try to copy row values to given fields ...

   if (pFields) {

      // if we have a buffer this means that values are mapped directly
      // and we don't need to copy them ...

      if (!pFields->pRowBuffer) {
         UINT count;
         for (count = 0; count < pFields->iCount; count++) {
            if (pFields->pList[count].vValue.pVoid) {
               switch (pFields->pList[count].eType) {
                  case DBTYPE_STR:
                  case DBTYPE_WSTR:
                     CopyString(
                        pFields->pList[count].vValue.pChar,
                        pFields->pList[count].iSize,count);
                     break;
                  case DBTYPE_BYTES:
                     CopyBLOBToString(
                        pFields->pList[count].vValue.pChar,
                        pFields->pList[count].iSize,count);
                     break;
                  case DBTYPE_I4:
                     *pFields->pList[count].vValue.pInt = AsLong(count);
                     break;
                  case DBTYPE_R8:
                     *pFields->pList[count].vValue.pDouble = AsDouble(count);
                     break;
                  case DBTYPE_DBTIMESTAMP:
                     GetDateTime(pFields->pList[count].vValue.pDatetime,count);
                     break;
                  case DBTYPE_IUNKNOWN:
                     CopyBLOBToString(
                        pFields->pList[count].vValue.pChar,
                        pFields->pList[count].iSize,count);
                     break;
               }
            }
         }
      }
   }

   return(pParentObj->hrLastHR == S_OK);
}  // end of fetch next row ...

/*
 * Function : void DbResult::Close()
 * Purpose  : Close the result set and release allocated resources ...
 * Date     : Feb/2k1  (ESob)
 */

void DbResult::Close()
{

   // release allocated storage objects resources ...

   if (nCols && pDbBindings) {
      ULONG count;
      for (count = 0; count < nCols; count++) {
         if (pDbBindings[count].pObject) {

            // most release the ISequentialStream object ...

            (*((ISequentialStream**)
               (pRowValues+pDbBindings[count].obValue)))->Release();

            pParentObj->g_pIMalloc->Free(pDbBindings[count].pObject);
         }
      }
   }
   nCols = 0;

   // Release row handles.

   if (prghRows) {
      if (*prghRows)
         pIRowset->ReleaseRows(cRowsObtained, prghRows, NULL, NULL, NULL);
   }

   if (pIRowset) {
      pIRowset->Release();
      pIRowset = NULL;
   }

   // Release the accessor

   if (pIAccessor) {
      if (hAccessor)
         pIAccessor->ReleaseAccessor(hAccessor, NULL);
      pIAccessor->Release();
      pIAccessor = NULL;
   }

   if (pDbBindings)
      delete [] pDbBindings;
   if (pDbBindStatus)
      delete [] pDbBindStatus;
   if (prghRows)
      delete [] prghRows;

   pDbBindings    = NULL;
   pDbBindStatus  = NULL;
   prghRows       = NULL;

   if (pColumnsInfo)
      pParentObj->g_pIMalloc->Free(pColumnsInfo);
   if (pColumnStrings)
      pParentObj->g_pIMalloc->Free(pColumnStrings);

   pColumnsInfo   = NULL;
   pColumnStrings = NULL;

   cCurrentRow    = 0;

   // free data buffer

   if (pRowValues)
      delete pRowValues;
   pRowValues = NULL;
}  // end of close result set ...

/*
 * Function : void DbResult::Free()
 * Purpose  : Close the result set and release allocated resources ...
 * Date     : Feb/2k1  (ESob)
 */

void DbResult::Free()
{
   Close();

   if (pIMultipleResults != NULL) {
      pIMultipleResults->Release();
      pIMultipleResults = NULL;
   }

   // free the command text ...

   if (pICommandText)
      pICommandText->Release();

   // reset all resources to NULL / undefine ...

   ResetResources();
}  // end of close and free allocated resources ...

#pragma endregion

// -----------------------------------------------------------------------------
#pragma region // Define the DbCommonQueries class


/// <function> long DBCommonQueries::GetScalar(
///               ClDBConnection &Connection,TCHAR *pQuery) </function>
/// <summary> Get a scalar value returned by executing the given query.
/// </summary>
/// <param="Connection"> Database connection (ClDBConnection&) </param>
/// <param="pQuery"> Supplied query (TCHAR*) </param>
/// <returns> Return the number of records in given table by using the
///    given query. </returns>
/// <date> Jan/2k4  ESob </date>

long DbCommonQueries::GetScalar(DbConnection &Connection,TCHAR *pQuery)
{
   long rcount = -1 ;

   // prepare fields and parameters

   DbFields *pFields = Connection.AllocFields(1);
   pFields->Set(0,&rcount);

   DbResult *pResult = Connection.Prepare(pQuery,pFields,NULL);

   if (pResult)
   {
      if (pResult->Execute())
      {
         pResult->Open();
         if (pResult->Next(pFields))
         {
            if (pResult->Eof())
            {
               rcount = 0;
            }
         }
      }
      pResult->Close();

      delete pResult;
   }

   return(rcount);
}  // end of GetTableRecordCount

/// <function> long DBCommonQueries::GetTableRecordCount(
///               TCHAR *pTableName) </function>
/// <summary> Get table record count. </summary>
/// <param="Settings"> Database settings (StDBSettings&) </param>
/// <param="pTableName"> Table name (TCHAR*) </param>
/// <returns> Return the number of records in given table. </returns>
/// <date> Jan/2k4  ESob </date>

long DbCommonQueries::GetTableRecordCount(
   DbSettings &Settings, TCHAR *pTableName)
{
   long rcount = 0;

   // connect to database

   DbConnection con;
   if (!con.Connect(Settings.pHost,Settings.pCatalog,Settings.pUserId,
      Settings.pPassword))
      return(rcount);

   // prepare query

   TCHAR pQuery[MaxStringLength+1] ;
   TCHAR *pSelect = TEXT("select count(*) from ");
   size_t l = Kif::Win32::String::GetLength(pSelect) ;
   _tcscpy_s(pQuery,MaxStringLength,pSelect);
   _tcscat_s(pQuery,MaxStringLength-l,pTableName);

   // execute query and get record count

   rcount = GetScalar(con,pQuery);

   // disconnect and release resources

   con.Disconnect();
   return(rcount);
}  // end of GetTableRecordCount

#pragma endregion

}  // end of Kif::Win32::OleDbClient namespace
}  // end of Kif::Win32::Data namespace
}  // end of Kif::Win32 namespace
}  // end of Kif namespace
